一个基于一个基于aliyun-oss-python-sdk的代码同步工具

基于阿里云OSS Python SDK开发的文件同步工具,支持多线程更新及指定文件类型同步,适用于代码文件或其他小文件的高效云端同步。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一个基于aliyun-oss-python-sdk的代码同步工具,可以指定同步文件的类型。(水平很烂,请轻喷 (:3 」∠) ) 要使用的话需要租一个阿里云oss2云存储服务,很便宜,9rmb/year 因为流量是走的开发者接口,所以流量免费,用来同步一些小文件很划算 我是用来同步代码文件的,当然其他类型的文件也可以。 交互方式采用命令行。 注意: 使用前须将 endpoint accessKeyId accessKeySecret 替换为自己的,以及需要在在oss2控制台上新建一个Bucket 然后将Bucket_name替换为自己的Bucket_name auth=oss2.Auth(accessKeyId,accessKeySecret) bucket=oss2.Bucket(auth,endpoint,Bucket_name)

扫描,更新文件都采用多线程的方式,注意线程不能开太多,会发生网络阻塞,且容易造成云端服务器无响应。

个人水平有限,有问题欢迎来交流。

oss2_sync_tool_v3.py.py

import os,shutil
import oss2
import logging
import my_utils as utils
import threading
#配置信息,注意替换为自己的
endpoint=''
accessKeyId=''
accessKeySecret=''
#local_path='C:/Users/pc/workspace/java workspace/'
local_path_list=['C:/Users/pc/workspace/java workspace/','C:/Users/pc/workspace/sublime/']#因为分割符的位置在下面是硬性指定的,所以最好保证列表中的目录是等深度的,不然就得单独处理,这里就对应45,125行,129,130,137,138行的索引5,深度改变了,索引也要改
temp_path='C:/Users/pc/oss2_sync_cache/'#缓存路径
cloud_path='my_src_code/'#云端文件夹

include_suffix=['c','cpp','java','py','txt','xml','properties','h','hpp']#待同步文件类型可以自行指定,进一步的,也可以对其他的文件信息进行个性化定制,如文件大小等


auth=oss2.Auth(accessKeyId,accessKeySecret)
bucket=oss2.Bucket(auth,endpoint,'xxxx')#这里的xxxx改成自己的bucket_name


src_file_list=[]
temp_file_list=[]
lock=threading.Lock()
def generate_cache(src_file_list,temp_file_list):
    for srcEntry,tempEntry in zip(src_file_list,temp_file_list):
        if not os.path.exists(tempEntry):#先看文件存在否
            lock.acquire()
            if not os.path.exists('/'.join(tempEntry.split('/')[:-1])):#再看目录存在否
                os.makedirs('/'.join(tempEntry.split('/')[:-1]))
            lock.release()
            with open(tempEntry,'w+') as f:#先创建空文件(因为不存在)
                f.close()

        if utils.newer(srcEntry,tempEntry) or os.path.getsize(tempEntry)==0:#如果源文件较新或目标文件为空文件(可能是之前刚创建的),才更新
            shutil.copy2(srcEntry,tempEntry)#copy2()会连带着源文件状态(时间戳)一起复制,copy+copystat

#扫描源目录,并生成临时存储信息到temp_file_list中,然后根据temp_file_list来更新临时文件
def src2temp(temp_path,thread_number=16):
    #1.扫描源目录,并将符合的文件路径存储到src_file_list中
    for entry_list in local_path_list:
        utils.scan(entry_list,src_file_list,include_suffix)
    for entry in src_file_list:
        temp_file_list.append(temp_path+'/'.join(entry.split('/')[5:]))
        #print(temp_path+'/'.join(entry.split('/')[5:]))

    assert(len(src_file_list)==len(temp_file_list))
    #更新临时文件,引入多线程处理
    tmp_s=[]
    tmp_d=[]
    threads=[]
    for i in range(thread_number):
        tmp_s.append(list())
        tmp_d.append(list())
    for i in range(len(src_file_list)):
        tmp_s[i%thread_number].append(src_file_list[i].replace('//','/'))
        tmp_d[i%thread_number].append(temp_file_list[i].replace('//','/'))
    for i in range(thread_number):
        threads.append(threading.Thread(target=generate_cache,args=(tmp_s[i],tmp_d[i],)))
    for t in threads:
        t.setDaemon(True)
        t.start()
    for t in threads:
        t.join()

def update_file(temp_file_list):
    for entry in temp_file_list:
        #拆分本地目录名和文件名,生成云端路径
        utils.update_file(bucket,
                    '/'.join(entry.split('/')[:-1])+'/',
                    entry.split('/')[-1],
                    (cloud_path+'/'.join(entry.split('/')[4:-1])+'/').replace('//','/'),
                    #//会导致云端生成空目录,所以替换
                    entry.split('/')[-1])

        #print('/'.join(entry.split('/')[:-1])+'/'+entry.split('/')[-1])
        #print((cloud_path+'/'.join(entry.split('/')[5:-1])+'/').replace('//','/')+entry.split('/')[-1])
        

#将临时文件同步更新到云端,不直接从源文件同步到云端,做了一个隔离,对源文件只有读权限,对临时文件才有读写权限
def temp2cloud(endpoint,accessKeyId,accessKeySecret,thread_number=16):
    #扫描源目录,并生成临时存储信息到temp_file_list中,然后根据temp_file_list来更新临时文件
    src2temp(temp_path)
    if not bucket.object_exists(cloud_path):
        bucket.put_object(cloud_path)

    tmp_c=[]
    threads=[]
    total_size=0
    for i in range(thread_number):
        tmp_c.append(list())
    for i in range(len(temp_file_list)):
        tmp_c[i%thread_number].append(temp_file_list[i].replace('//','/'))
        total_size+=os.path.getsize(temp_file_list[i].replace('//','/'))
    for i in range(thread_number):
        threads.append(threading.Thread(target=update_file,args=(tmp_c[i],)))#特别注意要有逗号
    for t in threads:
        t.setDaemon(True)
        t.start()
    for t in threads:
        t.join()
    
    print('scan file number: {0}\ntotal size: {0} kB'.format(len(temp_file_list),total_size/1000))



def print_info():
    print('*************** oss2_sync_tool_v3 **********************')
    print('* input ls:  list the files that need to be updated.   *')
    print('* input ls -u:  update list after execute ls.          *')
    print('* input update:  update all files.                     *')
    print('* input cfg-s:  configure include_suffix.              *')
    print('* input cfg-n:  configure oss2-bucket-name.            *')
    print('* input restore:  restore to the original state.       *')
    print('* input clear:  clear all temp files.                  *')
    print('* input show-info:  set up display information or not. *')
    print('* input help:  show the help information.              *')
    print('* input q:  exit the program.                          *')
    print('*************************************** --xycode *******')

ls_update_list=[]#待更新列表
def ls_part(ls_list):
    for entry in ls_list:#ls_list相当于之前的src_file_list,所以下面是[5:]
        temp_cloud_path=(cloud_path+'/'.join(entry.split('/')[5:-1])+'/').replace('//','/')+entry.split('/')[-1]
        #print(temp_cloud_path)
        if not bucket.object_exists(temp_cloud_path):#云端不存在的话
            print(entry)
            generate_cache([entry],[temp_path+'/'.join(entry.split('/')[5:])])#先生成缓存
            ls_update_list.append(temp_path+'/'.join(entry.split('/')[5:]))
            continue
        statinfo=os.stat(entry)
        local_last_modified_time=int(statinfo.st_mtime)-28800#统一转换为以秒为单位,北京时间-8小时=GMT
        cloud_last_modified_time=utils.date_to_num(bucket.get_object(temp_cloud_path))
        if local_last_modified_time>cloud_last_modified_time:#本地文件较新
            print(entry)
            shutil.copy2(entry,temp_path+'/'.join(entry.split('/')[5:]))
            ls_update_list.append(temp_path+'/'.join(entry.split('/')[5:]))


def ls(thread_number=24):
    ls_list=[]
    for entry_list in local_path_list:
        utils.scan(entry_list,ls_list,include_suffix)
    tmp_l=[]
    threads=[]
    for i in range(thread_number):
        tmp_l.append(list())
    for i in range(len(ls_list)):#分配任务
        tmp_l[i%thread_number].append(ls_list[i].replace('//','/'))
    for i in range(thread_number):
        threads.append(threading.Thread(target=ls_part,args=(tmp_l[i],)))#特别注意要有逗号
    for t in threads:
        t.setDaemon(True)
        t.start()
    for t in threads:
        t.join()

def clear():
    str=input('clear all temp file?(y/n)\n')
    if str in ['Y','y']:
        if os.path.exists(temp_path):
            shutil.rmtree(temp_path)
        os.makedirs(temp_path)
    else:
        return

store_include_suffix=[]
def cfg_suffix():
    global include_suffix #这句必须有,否则报错
    global store_include_suffix
    store_include_suffix=include_suffix.copy()
    include_suffix.clear()
    s=input('please input suffix-name,separated by space:\n')
    include_suffix=s.split(' ')

'''
以后可以考虑用命令行设置
endpoint
accessKeyId
accessKeySecret
指定bucket name
以及用命令行恢复
这里只是恢复include_suffix
'''
def restore():
    global include_suffix#注意
    global store_include_suffix#注意
    include_suffix=store_include_suffix.copy()


def interact(show_info=True):
    if show_info: print_info()
    command=input('please input a legal commmad:\n')
    if command=='ls':
        ls()
        if show_info:
            interact(True)
        else:
            interact(False)
    elif command=='ls -u':
        if ls_update_list==[]:
            print("ls_update_list is empty,no need to update.")
            print("please check for updates to execute command \'ls\' firstly.")
        else:
            update_file(ls_update_list)
        ls_update_list.clear()
        if show_info:
            interact(True)
        else:
            interact(False)
    elif command=='cfg-s':
        cfg_suffix()
        if show_info:
            interact(True)
        else:
            interact(False)
    elif command=='cfg-n':
        global bucket
        oss2_bucket_name=input('please input oss2-bucket-name:\n(legal name: [\'xycode1\',\'xycode2\'])\n')
        if oss2_bucket_name in ['xycode1','xycode2']:
            bucket=oss2.Bucket(auth,endpoint,oss2_bucket_name)
        else:
            print('illegal oss2-bucket-name!')
        if show_info:
            interact(True)
        else:
            interact(False)
    elif command=='restore':
        restore()
        if show_info:
            interact(True)
        else:
            interact(False)
    elif command=='update':
        src_file_list.clear()
        temp_file_list.clear()
        if not os.path.exists(temp_path):
            os.makedirs(temp_path)
        #src2temp(temp_path)
        temp2cloud(endpoint,accessKeyId,accessKeySecret)
        if show_info:
            interact(True)
        else:
            interact(False)
    elif command=='clear':
        clear()
        if show_info:
            interact(True)
        else:
            interact(False)
    elif command=='help':
        print_info()
        if show_info:
            interact(True)
        else:
            interact(False)
    elif command=='show-info':
        s=input('show?(y/n)\n')
        if s in ['Y','y']:
            interact(True)
        else:
            interact(False)
    elif command=='q':
        return
    else:
        print('incorrect command,please input again.')
        if show_info:
            interact(True)
        else:
            interact(False)


if __name__ == '__main__':
    # 设置日志等级为CRITICAL
    log_file_path = "log.log"
    oss2.set_file_logger(log_file_path, 'oss2', logging.CRITICAL)
    interact()


my_utils.py

import os,shutil,re
import oss2
import time
def date_to_num(GetObjectResult):
	result=' '.join(GetObjectResult.headers['Last-Modified'].split(' ')[1:-1])
	date_result=time.strptime(result,'%d %b %Y %H:%M:%S')
	date_num=time.mktime(date_result)
	return int(date_num)

#将本地文件同步到云端
def update_file(bucket,local_file_path,local_filename,cloud_file_path,cloud_filename):
	statinfo=os.stat(local_file_path+local_filename)
	#注意时区的转换
	# local_last_modified_time=time.mktime(time.localtime(statinfo.st_mtime))-28800#统一转换为以秒为单位,北京时间-8小时=GMT
	local_last_modified_time=int(statinfo.st_mtime)-28800#统一转换为以秒为单位,北京时间-8小时=GMT
	with open(oss2.to_unicode(local_file_path+local_filename),'rb') as f:
		if bucket.object_exists(cloud_file_path+cloud_filename):#云端已经存在该文件
			cloud_last_modified_time=date_to_num(bucket.get_object(cloud_file_path+cloud_filename))
			# print(local_last_modified_time,cloud_last_modified_time)
			if local_last_modified_time>cloud_last_modified_time:
				#如果本地的文件较新才更新到云端
				print('update '+local_filename+'.')
				bucket.put_object(cloud_file_path+cloud_filename,f)#将本地文件更新到云端cloud_file_path+cloud_filename
			else:
				print(cloud_filename+' already up-to-date.')
		else:
			print(cloud_filename+' does not exist,start upload...')
			bucket.put_object(cloud_file_path+cloud_filename,f)#将本地文件上传到云端cloud_file_path+cloud_filename

#判断源文件与临时文件哪个较新
def newer(srcEntry,destEntry):
    statinfo=os.stat(srcEntry)
    src_last_modified_time=int(statinfo.st_mtime)
    statinfo=os.stat(destEntry)
    dest_last_modified_time=int(statinfo.st_mtime)
    return src_last_modified_time>dest_last_modified_time

#扫描目录的函数,并将符合的文件路径存储到src_file_list中
def scan(path,src_file_list,include_suffix):
    if not os.path.exists(path):
        return
    if os.path.isdir(path):
       for entry in os.listdir(path):
           scan(path+'/'+entry,src_file_list,include_suffix)
    else:
        if path.split('.')[-1] in include_suffix:
            src_file_list.append(path)
            #print(path)

详见我的github主页:GitHub - xycodec/oss2_sync_tool_v4: 一个基于aliyun-oss-python-sdk的同步工具

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值